/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkNamespace } from 'arkanalyzer';
import { ModifierType } from 'arkanalyzer/lib/core/model/ArkBaseModel';
import { Language } from 'arkanalyzer/lib/core/model/ArkFile';
import { Rule } from '../../model/Rule';
import { BaseChecker, BaseMetaData } from '../BaseChecker';
import { Defects, IssueReport } from '../../model/Defects';
import { NamespaceMatcher, MatcherCallback, MatcherTypes } from '../../matcher/Matchers';
import { RuleListUtil } from '../../utils/common/DefectsList';

const gMetaData: BaseMetaData = {
  severity: 2,
  ruleDocPath: 'docs/no-namespace.md',
  description: 'ES2015 module syntax is preferred over namespaces.',
};
type Options = {
  /** 是否允许在 declaration 中使用 namespace */
  allowDeclarations?: boolean;
  /** 是否允许在 .d.ts 文件中使用命名空间 */
  allowDefinitionFiles?: boolean;
};

export class NoNameSpaceCheck implements BaseChecker {
  readonly metaData: BaseMetaData = gMetaData;
  public rule: Rule;
  public defects: Defects[] = [];
  public issues: IssueReport[] = [];
  private defaultOptions: Options = {
    allowDeclarations: false,
    allowDefinitionFiles: true,
  };
  private options: Options = { ...this.defaultOptions };

  private namespaceMatcher: NamespaceMatcher = {
    matcherType: MatcherTypes.NAMESPACE,
  };

  public registerMatchers(): MatcherCallback[] {
    const matchFileCb: MatcherCallback = {
      matcher: this.namespaceMatcher,
      callback: this.check
    }
    return [matchFileCb];
  }

  private parseOptions(): void {
    if (this.rule?.option?.[0]) {
      const ruleOption = this.rule.option[0] as Options;
      this.options = {
        allowDeclarations: ruleOption.allowDeclarations ?? this.defaultOptions.allowDeclarations,
        allowDefinitionFiles: ruleOption.allowDefinitionFiles ?? this.defaultOptions.allowDefinitionFiles,
      };
    };
  }

  public check = (ns: ArkNamespace): void => {
    this.parseOptions();
    
    const filePath = ns.getDeclaringArkFile()?.getFilePath() ?? '';
    const name = ns.getName();
    const code = ns.getCode();

    if (!this.isValidFile(ns) || name === 'global' || this.isWordQuoted(code, name)) {
      return;
    }

    const isDeclaration = this.isDeclaration(ns);
    const isDefinitionFile = filePath.endsWith('.d.ts');

    if ((isDeclaration && this.options.allowDeclarations) ||
        (isDefinitionFile && this.options.allowDefinitionFiles)) {
      return;
    }

    this.addIssueReport(ns.getLine(), ns.getColumn(), filePath);
  };

  private isValidFile(ns: ArkNamespace): boolean {
    return ns.getDeclaringArkFile().getLanguage() === Language.TYPESCRIPT ||
    ns.getDeclaringArkFile().getLanguage() === Language.JAVASCRIPT;
  }

  public isWordQuoted(text: string, word: string): boolean {
    const pattern = new RegExp(`['"]\\b${this.escapeRegExp(word)}\\b['"]`);
    return pattern.test(text);
  }

  public escapeRegExp(str: string): string {
    return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  }

  public isDeclaration = (ns: ArkNamespace): boolean => {
    let cur: ArkNamespace | null = ns;
    while (cur) {
      if (cur.getModifiers() === ModifierType.DECLARE) {
        return true;
      }
      cur = cur.getDeclaringArkNamespace();
    }
    return false;
  };

  private addIssueReport(line: number, col: number, filePath: string): void {
    const severity = this.rule.alert ?? this.metaData.severity;
    const defect = new Defects(line, col, col, this.metaData.description,
      severity, this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, false);
    this.issues.push(new IssueReport(defect, undefined));
    RuleListUtil.push(defect);
  }
}